package org.javaboy.demo.page;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.*;

/**
 * MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler
 */
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        //这是我们即将要执行的 XML 中的 SQL 封装成的对象
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObj = (Object) args[1];
        //这是调用方传入的分页参数
        RowBounds rowBounds = (RowBounds) args[2];
        //如果 rowBounds 是 RowBounds.DEFAULT，即认为是想要内存分页，那么就执行默认的处理流程
        if (rowBounds != RowBounds.DEFAULT) {
            Executor executor = (Executor) invocation.getTarget();
            BoundSql boundSql = ms.getBoundSql(parameterObj);
            if (rowBounds instanceof PageRowBounds) {
                //说明是要物理分页
                //构建查询总记录数的 MappedStatement，因为 XML 文件中写的查询 SQL 等，最终都会被封装为 MappedStatement
                MappedStatement countMs = newMappedStatement(ms, Long.class);
                CacheKey cacheKey = executor.createCacheKey(countMs, parameterObj, rowBounds, boundSql);
                String countSql = "select count(*) from (" + boundSql.getSql() + ") temp";
                BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObj);
                List<Object> result1 = executor.query(countMs, parameterObj, RowBounds.DEFAULT, (ResultHandler) args[3], cacheKey, countBoundSql);
                Long total = (Long) result1.get(0);
                ((PageRowBounds) rowBounds).setTotal(total);
                CacheKey cacheKey2 = executor.createCacheKey(ms, parameterObj, rowBounds, boundSql);
                cacheKey2.update("RowBounds");
                String pageSql = boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
                BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObj);
                List list = executor.query(ms, pageSql, RowBounds.DEFAULT, (ResultHandler) args[3], cacheKey2, pageBoundSql);
                return list;
            }
        }
        return invocation.proceed();
    }

    private MappedStatement newMappedStatement(MappedStatement ms, Class<Long> longClass) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_count", ms.getSqlSource(), ms.getSqlCommandType());
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), longClass, new ArrayList<>()).build();
        builder.resource(ms.getResource())
                .fetchSize(ms.getFetchSize())
                .statementType(ms.getStatementType())
                .timeout(ms.getTimeout())
                .parameterMap(ms.getParameterMap())
                .resultSetType(ms.getResultSetType())
                .cache(ms.getCache())
                .flushCacheRequired(ms.isFlushCacheRequired())
                .useCache(ms.isUseCache())
                .resultMaps(Arrays.asList(resultMap));
        return builder.build();
    }
}
